package id.co.rho.scoring.cid.client.model;

import com.extjs.gxt.ui.client.data.ModelData;

import id.co.rho.scoring.cid.changecontrol.entitychange.UpdateEntityChange;
import id.co.rho.scoring.cid.changecontrol.entitychange.propertychange.PropertyChange;

import com.extjs.gxt.ui.client.data.BaseModel;
import com.extjs.gxt.ui.client.util.Util;

import java.io.Serializable;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.HashSet;
import java.util.Set;

public class ChangeTrackingModel extends BaseModel {
    private UpdateEntityChange change;

    public ChangeTrackingModel() {
        change = new UpdateEntityChange();
    }

    public ChangeTrackingModel(UpdateEntityChange change) {
    	setChange(change);
    }

    public ChangeTrackingModel(ModelData data) {
        this();
        for (String key : data.getPropertyNames()) {
            setOriginal(key, data.get(key));
        }
    }

    public void setChange(UpdateEntityChange change) {
    	this.change = change;
        setSilent(true);
        for (PropertyChange pc : change.getPropertyChanges()) {
            super.set(pc.getPropertyName(), pc.getNewValue());
        }
        setSilent(false);
	}

	public UpdateEntityChange getChanges() {
        return change;
    }

    public void setOriginal(String propName, Object value) {
        // no event propagation please.
        setSilent(true); // <- to prevent event propagation.
        PropertyChange pc = change.getRecordedChange(propName);
        // if newValue exists, we must not override it.
        if (pc != null) {
            pc.setOldValue((Serializable) value);
        } else {
            super.set(propName, value);
        }
        setSilent(false);
    }

    public Object getOriginal(String propName) {
        PropertyChange pc = change.getRecordedChange(propName);
        if (pc != null) {
            return pc.getOldValue();
        }

        return get(propName);
    }

    @SuppressWarnings("unchecked")
    @Override
    public <X> X set(String name, X newValue) {
        // a Change record should be created, only if the new value is different than old value.
        X oldValue;
        PropertyChange pChange = change.getRecordedChange(name);
        if (pChange != null) {
            oldValue = (X) pChange.getOldValue();
        } else {
            oldValue = super.get(name);
        }

        if (!equalsUsingComparableFirst(oldValue, newValue)) {
            // not equal, record change
            change.recordChange(name, (Serializable) oldValue, (Serializable) newValue);
        } else {
            // equal, the value is reverted back to original value.
            // a record should not be created.
            // if a propertyChange record has already created, then delete.
            if (pChange != null) {
                change.removeRecordedChange(name);
            }
        }

        return super.set(name, newValue);
    }

    private static boolean equalsUsingComparableFirst(Object oldValue, Object newValue) {
        if (oldValue != null && newValue != null) {
        	if(oldValue instanceof Double){
        		Double oldVal = (Double)oldValue;
        		Double newVal = new Double(""+newValue);
        		return oldVal.compareTo(newVal)==0;
        	}
        	else if(oldValue instanceof BigDecimal){
        		BigDecimal oldVal = (BigDecimal)oldValue;
        		BigDecimal newVal = new BigDecimal(""+newValue);

                oldVal = oldVal.setScale(11, RoundingMode.HALF_UP);
                newVal = newVal.setScale(11, RoundingMode.HALF_UP);
        		return oldVal.toString().equals(newVal.toString());
        	}
        	else if(oldValue instanceof Comparable && newValue instanceof Comparable) {
	            Comparable oldComparable = (Comparable) oldValue;
	            Comparable newComparable = (Comparable) newValue;
	            return oldComparable.compareTo(newComparable)==0;
        	}
        }
        return Util.equalWithNull(oldValue, newValue);
    }

    public boolean isDirty() {
        return change.getPropertyChanges().size() > 0 || change.isNewEntity() || change.isDeleteEntity();
    }

    public Set<String> getDirtyProperties() {
        HashSet<String> result = new HashSet<String>();
        for(PropertyChange p : change.getPropertyChanges()) {
            result.add(p.getPropertyName());
        }
        return result;
    }

    public void setOriginalIfNotSet(String propertyName, Object value) {
        if(getOriginal(propertyName)==null) {
            setOriginal(propertyName, value);
        }
    }

    public boolean isDeleteEntity() {
        return change.isDeleteEntity();
    }

    public void setDeleteEntity(boolean deleteEntity) {
        change.setDeleteEntity(deleteEntity);
    }
}
